package cn.sylinx.horm.core.stat;

import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;

import cn.sylinx.horm.config.OrmConfig;
import cn.sylinx.horm.config.OrmConfigHolder;
import cn.sylinx.horm.util.GLog;
import cn.sylinx.horm.util.StrKit;

/**
 * SQL 运行时长监控
 * 
 * @author johnhan
 *
 */
class SqlStatExecutor {

    private final ConcurrentHashMap<String, SqlStatInfo> executedSql = new ConcurrentHashMap<>();

    private final AtomicBoolean started = new AtomicBoolean(false);

    private Thread detectSlowlySqlThread = null;

    boolean isStarted() {
        return started.get();
    }

    void start() {

        if (!started.compareAndSet(false, true)) {
            GLog.info("SqlStatExecutor has started.");
            return;
        }

        GLog.info("SQL统计启动中 ... ");

        detectSlowlySqlThread = new Thread(this::detectSlowlySql);
        detectSlowlySqlThread.setDaemon(true);
        detectSlowlySqlThread.start();

        GLog.info("SQL统计已启动");
    }

    // 释放资源
    void stop() {
        if (started.compareAndSet(true, false)) {
            if (detectSlowlySqlThread != null) {
                GLog.info("SQL统计停止中...");
                detectSlowlySqlThread.interrupt();
            }
        }
    }

    // 接收执行开始sql
    private void receiveSqlBefore(String seq, String datasoureName, String nativeSql, Long time) {

        long current = System.currentTimeMillis();

        if (StrKit.isBlank(seq) || StrKit.isBlank(datasoureName) || StrKit.isBlank(nativeSql) || time > current) {
            GLog.error("参数异常，seq：{}，datasoureName：{}，time：{}，nativeSql：{}", seq, datasoureName, time, nativeSql);
            return;
        }

        SqlStatInfo sqlStatInfo = new SqlStatInfo();
        sqlStatInfo.setNativeSql(nativeSql);
        sqlStatInfo.setSeq(seq);
        sqlStatInfo.setDatasoureName(datasoureName);
        sqlStatInfo.setTime(time);

        executedSql.put(seq, sqlStatInfo);
    }

    // 接收执行完毕sql
    private void receiveSqlAfter(String seq, String datasoureName, String nativeSql, Long usedTime, Throwable error) {

        if (StrKit.isBlank(seq)) {
            GLog.error("参数异常，seq：{}", seq);
            return;
        }

        // 执行完毕直接移除
        executedSql.remove(seq);

        // 可以记录sql最终执行完毕所用时间
        onExecuteDone(seq, datasoureName, nativeSql, usedTime, error);
    }

    void receiveSql(String type, String seq, String datasoureName, String nativeSql, Long time, Throwable error) {

        if (StrKit.isBlank(type)) {
            GLog.error("参数异常，type不能为空");
            return;
        }

        if (SqlStatInfo.SQL_TYPE_BEFORE.equals(type)) {
            receiveSqlBefore(seq, datasoureName, nativeSql, time);
        } else if (SqlStatInfo.SQL_TYPE_AFTER.equals(type)) {
            receiveSqlAfter(seq, datasoureName, nativeSql, time, error);
        } else {
            GLog.error("参数异常，type：{}", type);
        }

    }

    void detectSlowlySql() {

        while (detectSlowlySqlThread != null && started.get() && !detectSlowlySqlThread.isInterrupted()) {

            try {
                for (String seq : executedSql.keySet()) {

                    SqlStatInfo sqlStatInfo = executedSql.get(seq);
                    if (sqlStatInfo == null) {
                        continue;
                    }

                    long diff = System.currentTimeMillis() - sqlStatInfo.getTime();
                    long threshold = OrmConfigHolder.getSqlExecuteTimeThreshold();

                    if (diff > threshold) {
                        // 删除对应对象
                        executedSql.remove(seq);
                        // 执行通知
                        onExecuteTimeout(sqlStatInfo, threshold);
                    }
                }
            } catch (Exception e) {
                GLog.error("detectSlowlySql error " + e.getMessage(), e);
            }

            LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(OrmConfig.DEFAULT_STAT_INTERNAL));
        }

        // 退出线程清空
        executedSql.clear();
        GLog.info("SQL统计已停止");
    }

    /**
     * 执行时长超时
     * 
     * @param sqlStatInfo
     * @param thresholdMillSeconds
     */
    protected void onExecuteTimeout(SqlStatInfo sqlStatInfo, long thresholdMillSeconds) {
        SqlExecutedHandlerManager.getSqlExecutedHandler().onTimeout(sqlStatInfo.getDatasoureName(),
                sqlStatInfo.getNativeSql(), thresholdMillSeconds);
    }

    /**
     * 执行完毕时触发
     * 
     * @param seq
     * @param datasoureName
     * @param nativeSql
     * @param usedTime
     * @param error
     */
    protected void onExecuteDone(String seq, String datasoureName, String nativeSql, Long usedTime, Throwable error) {
        SqlExecutedHandlerManager.getSqlExecutedHandler().onDone(datasoureName, nativeSql, usedTime, error);
    }
}
